/*
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#pragma once
#include "../NTree.h"
#include "../Relations.h"
#include "BoxTests.h"

template<class VecType>
class Cylinder
{
private:
	const typename VecType::scalarType rSquared;
	const float radius;
	const typename VecType::scalarType length;
	const typename VecType::scalarType absLength;
	const unsigned int iAxis;
	Box<VecType> aabb;
public:
	Cylinder() : rSquared(0), radius(0), length(0), absLength(0), iAxis(0) {}

	Cylinder(float radius, float length, unsigned int iAxis) :
				rSquared((typename VecType::scalarType)(radius*radius)),
				radius(radius), length((typename VecType::scalarType)length), absLength((typename VecType::scalarType)abs(length)), iAxis(iAxis)
	{
		assert(iAxis < VecType::dimension);
		//calc aabb
		VecType vExt, vNegExt;
		for (unsigned int iDim = 0; iDim < VecType::dimension; iDim++)
		{
			if(iDim==iAxis)
			{
				vExt[iDim]=(typename VecType::scalarType)length;
				vNegExt[iDim]=0;
			}
			else
			{
				vExt[iDim]=(typename VecType::scalarType)radius;
				vNegExt[iDim]=(typename VecType::scalarType)-radius;
			}
		}
		aabb = Box<VecType>(vNegExt, vExt);
	}

	bool
	testPoint(const VecType& v) const
	{
		const int& iAxComp=v.getComponent(iAxis);
		if(!((iAxComp>=0 && length>=0) || (iAxComp<=0 && length<=0)))
			return false;

		if(abs(iAxComp)>absLength)
			return false;

		typename VecType::scalarType iSquaredLength=0;
		for (unsigned int iDim = 0; iDim < VecType::dimension; iDim++)
			if(iDim!=iAxis)
			{
				const typename VecType::scalarType x=v.getComponent(iDim);
				iSquaredLength+=x*x;
			}

		return (iSquaredLength <= rSquared);
	}

	RangeRelation
	testBox(const Box<VecType>& box) const
	{
		//check bounds
		RangeRelation aabbRel=BoxTests<VecType>::boxRelation(box, aabb);
		switch(aabbRel)
		{
		case Surrounds:
			return Surrounds;
		case Outside:
			return Outside;
		default:
			break;
		}
		//check points of Cylinder
		//kinda early-out
		unsigned int nInside=0,
			nOutside=0;
		for(unsigned int iCorner=0; iCorner<VecType::childCount; iCorner++)
		{
			if((unsigned int)abs(nInside-nOutside)!=iCorner)
				break;//mixed case can directly be aborted
			if(testPoint(BoxTests<VecType>::getBoxVertex(box, iCorner)))
				nInside++;
			else
				nOutside++;
		}
		if(nInside==VecType::childCount)
			return Inside;
		if(nOutside!=VecType::childCount)
			return Overlaps;//mixed points
		//now all box vertices are outside
		//there can still be overlapping cases
		//due to the box faces intersecting with the cylinder

		//for each face of the box
		nInside=0;
		nOutside=0;
		unsigned int nTested=0;
		for (unsigned int iDim = 0; iDim < VecType::dimension; iDim++)
		{
			for (int iDir = 0; iDir < 2; iDir++)
			{
				int iAxisDim=-1;
				Box<typename VecType::lowerDim> rect;
				typename VecType::scalarType dist;
				if(iDir)
					dist=box.max.getComponent(iDim);
				else
					dist=box.min.getComponent(iDim);
				for (unsigned int i = 0, j=0; i < VecType::dimension; i++)
				{
					if(i==iDim)
						continue;
					rect.min[j]=box.min.getComponent(i);
					rect.max[j]=box.max.getComponent(i);
					if(iAxis==i)
						iAxisDim=j;
					j++;
				}
				RangeRelation rel;
				//project radius
				//decide how this face is positioned in relation to cylinder axis
				if(iDim==iAxis)
				{
					//intersected cylinder shape is a circle
					if(dist<0 || dist>length)
						continue;//this geometry can not be intersected by the plane
					Sphere<typename VecType::lowerDim> sph(rSquared);
					rel=sph.testBox(rect);
				}
				else
				{
					//intersected cylinder shape is a rectangle
					typename VecType::scalarType projectedRSquared=rSquared-dist*dist;
					float x=sqrtf((float)projectedRSquared);
					typename VecType::lowerDim vMax(x), vMin(-x);
					vMax[iAxisDim]=length;
					vMin[iAxisDim]=0;
					Box<typename VecType::lowerDim> projRect(vMin, vMax);
					if(projectedRSquared<0)
						continue;//this geometry can not be intersected by the Cylinder/circle
					rel=BoxTests<typename VecType::lowerDim>::boxRelation(rect, projRect);
				}
				switch(rel)
				{
				case Inside:
					nInside++;
					break;
				case Outside:
					nOutside++;
					break;
				default:
					return Overlaps;
				}
				nTested++;
			}
		}
		if(nOutside==nTested)
			return Outside;
		//mixed
		return Overlaps;
	}
};
